I Deployed a `coturn` Server to AWS

#coding #theWeb #learning #devOps #aws

Thanks to https://gabrieltanner.org/blog/turn-server/, last night I successfully deployed my first coturn server on an aws ec2 instance. Why you might ask? I am building a thing which needs a WebRTC Peer Connection! Everything in the guide worked as advertised, but I want to have this repeatable. While Terraform is no longer the open source sweetheart it was when I started learning it 6 months ago, I wanted to build on the little knowledge I have of it. Plus I'm using it for personal reasons so it's still fine as far as licenses are concerned.

Scripting the setup

please ignore the broken syntax highlighting and random backticks. There's a bug somewhere and I'm in the process of fixing it but the bug is breaking my whole blog so I'd rather this one post be a bit broken than the whole blog. Thanks!

Maybe there is a better way, but it seems straightforward to add an init template that runs when the instance starts. The template is a bash script that installs and configures the coturn server, as well as certbot as the article above suggests. One thing that will need to be done in the terraform is opening the proper ports. The default security group won't have the proper config so make sure not to skip that part, otherwise you'll see timeout errors on the testing site.

You can choose to skip user credentials on the turnserver depending on your use case. I'm going to set them up via the suggested CLI args in the "docs", which as far as I can tell is limited to this very well commented .conf file.

This is meant for running as an init script on aws ec2. I am using ubuntu since I don't want to deal withyum or building the coturn source.

Feel free to use the code below. No guarantees or warranties 😊.

The Script

TLDR: install coturn and certbot, configure them, and flip the ON switch!

  1
  2#!/bin/sh
  3
  4# shellcheck disable=SC2154
  5
  6  
  7
  8echo 'Initializing turn server...'
  9
 10  
 11
 12set -e
 13
 14  
 15
 16#ubuntu needs root
 17
 18sudo -i
 19
 20  
 21
 22echo 'Installing stuff...'
 23
 24#install coturn and cerbot
 25
 26apt-get update -y
 27
 28apt-get install coturn certbot -y
 29
 30  
 31
 32echo 'Setting config...'
 33
 34# enable the server
 35
 36cat >> /etc/default/coturn << EOF
 37
 38TURNSERVER_ENABLED=1
 39
 40EOF
 41
 42  
 43
 44# start the service
 45
 46systemctl start coturn
 47
 48  
 49
 50#get the ip of the current ec2 instace
 51
 52PUBLIC_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)
 53
 54INTERNAL_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
 55
 56DOMAIN=${domain}
 57
 58FQDN=${subdomain}.${domain}
 59
 60EMAIL=${email}
 61
 62COTURN_USER=${username}
 63
 64COTURN_PASS=${password}
 65
 66# Don't forget any DNS related credentials
 67  
 68
 69USER_INFO=$COTURN_USER:$COTURN_PASS
 70
 71# or for an encrypted pw: 
 72# USER_INFO=$(turnadmin -k -u "$COTURN_USER" -r "$DOMAIN" -p "$COTURN_PASS" | head -n 1)
 73
 74  
 75
 76# set the config
 77
 78cat >> /etc/turnserver.conf << EOF
 79
 80realm=$DOMAIN
 81
 82server-name=$FQDN
 83
 84external-ip=$PUBLIC_IP/$INTERNAL_IP
 85
 86cert=/etc/coturn/certs/$FQDN.cert
 87
 88pkey=/etc/coturn/certs/$FQDN.key
 89
 90user=$USER_INFO
 91
 92lt-cred-mech
 93
 94fingerprint
 95
 96EOF
 97
 98  
 99
100echo 'Creating DNS record...'
101
102# update the dns records at your DNS provider with the public ip
103
104
105# wait so that we can ensure the dns record is created and propogated
106# might not be necessary but seemed safer than not waiting
107
108sleep 30
109
110  
111
112echo 'Creating cert...'
113
114# set the cert
115
116certbot certonly -n --agree-tos --standalone --preferred-challenges http \
117
118--deploy-hook "systemctl restart coturn" \
119
120-d "$FQDN" \
121
122-m "$EMAIL"
123
124  
125
126# was facing an issue like this without copying the certs to a dir
127# that coturn could read:
128# https://github.com/coturn/coturn/issues/1139
129
130mkdir -p /etc/coturn/certs
131
132cp "/etc/letsencrypt/live/$FQDN/cert.pem" "/etc/coturn/certs/$FQDN.cert"
133
134cp "/etc/letsencrypt/live/$FQDN/privkey.pem" "/etc/coturn/certs/$FQDN.key"
135
136chown turnserver -R "/etc/coturn/certs"
137
138chmod 700 -R "/etc/coturn/certs"
139
140  
141
142service coturn restart
143
144  
145
146echo 'Done!'
147

The Terraform

And the terraform: TLDR: Spins up a keypair to be able to ssh into our ec2 instance, and the proper iam roles and permissions to do things in the least permissive way. A security group is also created with the proper ports open for our coturn server to talk to the rest of the web.

  1
  2terraform {
  3  required_providers {
  4    aws = {
  5      source  = "hashicorp/aws"
  6      version = "~> 5.0"
  7    }
  8  }
  9
 10  required_version = ">= 1.2.0"
 11}
 12
 13provider "aws" {
 14  region = var.aws_region
 15}
 16
 17locals {
 18  user_data = templatefile("${path.module}/init.tftpl", {
 19    subdomain        = var.subdomain
 20    domain           = var.domain
 21    username         = var.coturn_user
 22    password         = var.coturn_pass
 23    email            = var.email
 24    # any extra vars needed for DNS
 25  })
 26}
 27
 28# key pair used to ssh in to the ec2 instance
 29resource "aws_key_pair" "deployer" {
 30  key_name   = "coturn_keypair"
 31  public_key = file(var.public_key)
 32}
 33
 34resource "aws_instance" "app_server" {
 35  instance_type = "t3.nano"
 36  # ubuntu 22.10
 37  ami                  = "ami-0fc5d935ebf8bc3bc"
 38  key_name             = aws_key_pair.deployer.key_name
 39  iam_instance_profile = aws_iam_instance_profile.coturn.name
 40  security_groups      = [aws_security_group.coturn_sg.name]
 41  root_block_device {
 42    delete_on_termination = true
 43  }
 44  # init script
 45  user_data = local.user_data
 46  tags = {
 47    Name = "Coturn"
 48  }
 49}
 50
 51# IAM roles and policy docs
 52
 53# IAM role for the instance profile
 54resource "aws_iam_role" "role" {
 55  name               = "coturn_iam_role_ec2"
 56  path               = "/"
 57  assume_role_policy = data.aws_iam_policy_document.assume_role.json
 58}
 59
 60# attach role to an instance profile for use with the ec2 instance
 61resource "aws_iam_instance_profile" "coturn" {
 62  name = "coturn_iam_profile_ec2"
 63  role = aws_iam_role.role.name
 64  tags = {
 65    Name = "CoturnAdmin"
 66  }
 67}
 68
 69# ec2 role
 70data "aws_iam_policy_document" "assume_role" {
 71  statement {
 72    sid    = 1
 73    effect = "Allow"
 74    principals {
 75      type        = "Service"
 76      identifiers = ["ec2.amazonaws.com"]
 77    }
 78
 79    actions = ["sts:AssumeRole"]
 80  }
 81}
 82
 83# security group to open ports
 84resource "aws_security_group" "coturn_sg" {
 85  name        = "coturn_sg"
 86  description = "security group for the coturn ec2 instance"
 87
 88  ingress {
 89    from_port   = 3478
 90    to_port     = 3479
 91    protocol    = "tcp"
 92    cidr_blocks = ["0.0.0.0/0"]
 93  }
 94
 95  ingress {
 96    from_port   = 3478
 97    to_port     = 3479
 98    protocol    = "udp"
 99    cidr_blocks = ["0.0.0.0/0"]
100  }
101
102  # tls
103  ingress {
104    from_port   = 5349
105    to_port     = 5350
106    protocol    = "tcp"
107    cidr_blocks = ["0.0.0.0/0"]
108  }
109
110  ingress {
111    from_port   = 80
112    to_port     = 80
113    protocol    = "tcp"
114    cidr_blocks = ["0.0.0.0/0"]
115  }
116
117  egress {
118    from_port   = 0
119    to_port     = 0
120    protocol    = "-1"
121    cidr_blocks = ["0.0.0.0/0"]
122  }
123}
124

And the tf vars file:

 1
 2variable "aws_region" {
 3  description = "AWS region"
 4  type        = string
 5  default     = "us-east-1"
 6}
 7
 8variable "public_key" {
 9  description = "path to AWS keypair public key"
10  type        = string
11  default     = "~/.ssh/aws_keypair.pub"
12}
13
14variable "domain" {
15  description = "the domain name of the server"
16  type        = string
17}
18
19variable "subdomain" {
20  description = "the subdomain of the server"
21  type        = string
22}
23
24variable "username" {
25  description = "the username for the lts creds"
26  type        = string
27}
28
29variable "password" {
30  description = "the pass for the lts creds"
31  type        = string
32}
33variable "email" {
34  description = "the email address for the ssl cert"
35  type        = string
36}
37

With these combined, you should be able to deploy a coturn server with the flick of the terraform apply wrist!

If you have a new A record on your DNS provider, that means at least that step worked.

If you need to SSH into the instance, make sure to open up SSH port 22 in the security group for your IP. You can do this in the terraform if you want, but I wouldn't expect to need to get in to the server much except to grab the encrypted password. Also the AWS console offers a nice "My IP" option. I know it's possible to code this into the terraform but it seemed like too much effort for not a lot gained.

Test the server with https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

Resources

These resources were extremely helpful in learning and implementing all of this WebRTC stuff:

https://gabrieltanner.org/blog/turn-server https://web.dev/articles/webrtc-basics https://web.dev/articles/webrtc-infrastructure https://codelabs.developers.google.com/codelabs/webrtc-web/#0 https://www.baeldung.com/webrtc https://webrtc.github.io/samples/ https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

And for actually implementing WebRTC in an application, specifically SvelteKit, these were helpful. I ended up using Socket.io instead of ws as implemented in the example:

https://github.com/suhaildawood/SvelteKit-integrated-WebSocket

This was helpful too as I had never worked with websockets before: https://joyofcode.xyz/using-websockets-with-sveltekit

Created on:
Last updated:
<< Go Back